<template>
  <section class="todoapp">
    <header class="header">
      <h1>协同待办事项</h1>
      <input
        type="text"
        class="new-todo"
        placeholder="添加新的待办事项（回车确认）"
        v-model="newTodo"
        @keyup.enter="addTodoHandler"
      />
    </header>
    <!-- 待办事项的列表 -->
    <section class="main" v-if="todolist.length">
      <ul class="todo-list">
        <li
          v-for="(item, index) in todolist"
          :key="index"
          :class="{
            completed: item.get('done'),
          }"
        >
          <div class="view">
            <input
              type="checkbox"
              class="toggle"
              :checked="Boolean(item.get('done'))"
              @change="toggleTogo(item as Y.Map<TodoItem>)"
            />
            <label>{{ item.get("text") }}</label>
            <button class="destroy" @click="deleteTodo(index)"></button>
          </div>
        </li>
      </ul>
    </section>
  </section>
</template>

<script setup lang="ts">
import * as Y from "yjs";
import { ref, onMounted } from "vue";

type TodoItem = {
  id: number; // 每一条待办事项的 id
  text: string; // 每一条待办事项的内容
  done: boolean; // 当前这一条待办事项是否完成
};

const doc = new Y.Doc(); // 创建一个 Yjs 副本
const todos = doc.getArray<Y.Map<TodoItem>>("todos"); // 创建一个数组类型的共享数据
const channel = new BroadcastChannel("yjs-todo-channel");

const todolist = ref<Y.Map<TodoItem>[]>([]);
const newTodo = ref(""); // 记录新的待办事项

function addTodoHandler() {
  // 需要排除用户什么都没有输入，直接按回车
  const trimmed = newTodo.value.trim(); // 先去除左右的空格
  if (!trimmed) return; // 如果去除了左右空格后啥都没有了，说明用户输入的内容为空，直接返回

  // 代码来到这里，说明是要添加新的待办事项，这里我们直接更新 Yjs 副本
  const newItem = new Y.Map();
  newItem.set("id", Date.now() + Math.random());
  newItem.set("text", trimmed);
  newItem.set("done", false);
  // 推入到 yjs 副本里面
  todos.push([newItem as Y.Map<TodoItem>]);
  newTodo.value = "";
}

/**
 * 根据当前 Yjs 的副本来更新本地 todolist
 */
function updateList() {
  todolist.value = todos.toArray();
}

/**
 * 切换当前待办事项的完成状态
 * @param item 当前的待办事项
 */
function toggleTogo(item: Y.Map<any>) {
  item.set("done", !item.get("done"));
}

/**
 * 删除某一条待办事项
 * @param index 对应的要删除的待办事项的下标
 */
function deleteTodo(index: number) {
  todos.delete(index, 1); // 仍然是更新 yjs 副本，yjs 副本一旦发生变化，会触发 observe，而 observe 会触发 updateList
}

onMounted(() => {
  updateList(); // 一开始挂载的时候，整个 todolist 就先根据 yjs 副本初始化一次
  todos.observeDeep(() => updateList());
  // 如果有新的更新，将新的更新广播出去
  doc.on("update", (update) => channel.postMessage(update));
  // 如果有更新过来，需要应用更新
  channel.addEventListener("message", (e) => {
    Y.applyUpdate(doc, e.data);
  });
});
</script>

<style scoped>
@import "./assets/todo.css";
</style>
